﻿// Microsoft Public License (Ms-PL)
//
// This license governs use of the accompanying software. If you use the software, you accept this license.
// If you do not accept the license, do not use the software.
//
// 1. Definitions
//
// The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here 
// as under U.S. copyright law.
//
// A "contribution" is the original software, or any additions or changes to the software.
//
// A "contributor" is any person that distributes its contribution under this license.
//
// "Licensed patents" are a contributor's patent claims that read directly on its contribution.
//
// 2. Grant of Rights
//
// (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations 
// in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to 
// reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution 
// or any derivative works that you create.
//
// (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in 
// section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed 
// patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution 
// in the software or derivative works of the contribution in the software.
//
// 3. Conditions and Limitations
//
// (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or 
// trademarks.
//
// (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the 
// software, your patent license from such contributor to the software ends automatically.
//
// (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and 
// attribution notices that are present in the software.
//
// (D) If you distribute any portion of the software in source code form, you may do so only under this license 
// by including a complete copy of this license with your distribution. If you distribute any portion of the 
// software in compiled or object code form, you may only do so under a license that complies with this license.
//
// (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express 
// warranties, guarantees or conditions. You may have additional consumer rights under your local laws which 
// this license cannot change. To the extent permitted under your local laws, the contributors exclude the 
// implied warranties of merchantability, fitness for a particular purpose and non-infringement.

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web.Services;
using System.Web.Services.Protocols;

namespace StructuredSolutions.ShippingAgent
{
    public abstract class AgentServiceBase : WebService
    {
        public const string DefaultNamespace = "http://structured-solutions.net/ShippingAgent/2008-09-18";
        protected const string OrderExportedText = "The order has been exported by Shipping Agent to {0}.";
        protected const string OrderInventoryExportedText = "The order item inventory has been exported by Shipping Agent to {0}.";
        protected const string OrderUpdateText = "The order has been changed by Shipping Agent: {0}";

        #region Web Methods

        /// <summary>
        /// Return orders placed since lastOrderNumber. If lastOrderNumber is null or an empty
        /// string, then return all orders from today.
        /// </summary>
        /// <param name="lastOrderNumber">The last order number in the previous results. May be
        /// null or an empty string if this is the first time this method is called.</param>
        /// <param name="maxCount">The maximum number of orders to return.</param>
        /// <returns>Up to maxCount OrderResults, sorted by order number; or an empty list or null 
        /// if there are no new orders.</returns>
        /// <remarks>
        /// <para>If maxCount orders are returned, the Shipper client will call this method again with the 
        /// same ranges and with lastOrderNumber set to the last order number in the previous result. In this
        /// case, use lastOrderNumber to calculate an appropriate new startOrderNumber (for example,
        /// startOrderNumber = lastOrderNumber + 1).</para>
        /// <para>Return a <see cref="SoapException"/> if the parameters are invalid.</para>
        /// </remarks>
        [WebMethod(Description = "Retrieve orders placed since lastOrderNumber.")]
        public virtual List<OrderResult> GetNewOrders(string lastOrderNumber, int maxCount)
        {
            try
            {
                return GetNewOrdersFromStore(lastOrderNumber, maxCount);
            }
            catch (ApplicationException ex)
            {
                throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
            }
            catch (Exception ex)
            {
                throw new DetailedSoapException("Cannot retrieve new orders.", SoapException.ServerFaultCode, ex);
            }
        }

        /// <summary>
        /// Return one order.
        /// </summary>
        /// <param name="orderId">The ID of the order.</param>
        /// <returns>The OrderResult.</returns>
        /// <exception cref="SoapException">If the orderID is not valid (such as blank or null).</exception>
        [WebMethod(Description = "Retrieve one order.")]
        public virtual OrderResult GetOrder(string orderId)
        {
            try
            {
                if (string.IsNullOrEmpty(orderId))
                    throw new ApplicationException("Invalid order ID.");
                return GetOrderFromStore(orderId);
            }
            catch (ApplicationException ex)
            {
                return new OrderResult { Success = false, Message = ex.Message };
            }
            catch (Exception ex)
            {
                return new OrderResult { Success = false, Message = ex.ToString() };
            }
        }

        /// <summary>
        /// Return the current inventory for all items in one order.
        /// </summary>
        /// <param name="orderId">The ID of the order.</param>
        /// <returns>A list of item Inventory objects or null if inventory is not tracked.</returns>
        /// <exception cref="SoapException">If the orderID is not valid (such as blank or null).</exception>
        [WebMethod(Description = "Retrieve the current inventory for all items in one order.")]
        public virtual InventoryResult GetOrderInventory(string orderId)
        {
            try
            {
                if (string.IsNullOrEmpty(orderId))
                    throw new ApplicationException("Invalid order ID.");
                return GetOrderInventoryFromStore(orderId);
            }
            catch (ApplicationException ex)
            {
                return new InventoryResult {Success = false, Message = ex.Message};
            }
            catch (Exception ex)
            {
                return new InventoryResult {Success = false, Message = ex.ToString()};
            }
        }

        /// <summary>
        /// Return all the orders that fall on and within the specified ranges.
        /// </summary>
        /// <param name="startOrderNumber">The starting order number; or null or an empty string to start at the first order number in the store.</param>
        /// <param name="endOrderNumber">The ending order number; or null or an empty string to end with the last order number in the store.</param>
        /// <param name="startOrderDate">The starting order date; or a date less than or equal to 1/1/1753 to start with the first order date in the store.</param>
        /// <param name="endOrderDate">The ending order date; or a date equal to or greater than 12/31/9999 to end with the last order date in the store.</param>
        /// <param name="lastOrderNumber">The last order number from the previous result; or null or an empty string to ignore this parameter.</param>
        /// <param name="maxCount">The maximum number of orders to return.</param>
        /// <returns>Up to maxCount Order's, sorted by order number; or an empty list or null 
        /// if there are no orders that fall on and within the specified ranges.</returns>
        /// <remarks>If maxCount orders are returned, the Shipper client will call this method again with the 
        /// same ranges and with lastOrderNumber set to the last order number in the previous result. In this
        /// case, use lastOrderNumber to calculate an appropriate new startOrderNumber (for example,
        /// startOrderNumber = lastOrderNumber + 1).
        /// </remarks>
        [WebMethod(Description = "Return the list of orders that fall on and within the specified ranges.")]
        public virtual List<OrderResult> GetOrdersByRange(string startOrderNumber, string endOrderNumber, DateTime? startOrderDate, DateTime? endOrderDate, string lastOrderNumber, int maxCount)
        {
            try
            {
                return GetOrdersByRangeFromStore(startOrderNumber, endOrderNumber, startOrderDate, endOrderDate, lastOrderNumber, maxCount);
            }
            catch (ApplicationException ex)
            {
                throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
            }
            catch (Exception ex)
            {
                throw new DetailedSoapException("Cannot retrieve orders by range.", SoapException.ServerFaultCode, ex);
            }
        }

        /// <summary>
        /// Return one specific package.
        /// </summary>
        /// <param name="orderId">The ID of the order.</param>
        /// <param name="packageId">The ID of the package.</param>
        /// <returns>A PackageResult.</returns>
        /// <exception cref="SoapException">If the orderId or packageId is not valid (such as blank or null).</exception>
        [WebMethod(Description = "Retrieve one package.")]
        public virtual PackageResult GetPackage(string orderId, string packageId)
        {
            try
            {
                if (string.IsNullOrEmpty(orderId))
                    throw new ApplicationException("Invalid order ID.");
                if (string.IsNullOrEmpty(packageId))
                    throw new ApplicationException("Invalid package ID.");
                return GetPackageFromStore(orderId, packageId);
            }
            catch (ApplicationException ex)
            {
                return new PackageResult {Success = false, Message = ex.Message};
            }
            catch (Exception ex)
            {
                return new PackageResult {Success = false, Message = ex.ToString()};
            }
        }

        /// <summary>
        /// Returns the complete list of tracking providers supported by the store. The
        /// Shipper client may assign any tracking provider to a package, regardless of
        /// how the shipping cost was initially calculated.
        /// </summary>
        /// <returns>The list of TrackingProvider's.</returns>
        [WebMethod(Description = "Retrieve the list of package tracking providers.")]
        public List<TrackingProvider> GetTrackingProviders()
        {
            try
            {
                return GetTrackingProvidersFromStore();
            }
            catch (ApplicationException ex)
            {
                throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
            }
            catch (Exception ex)
            {
                throw new DetailedSoapException("Cannot retrieve tracking providers.", SoapException.ServerFaultCode, ex);
            }
        }

        /// <summary>
        /// Record package information.
        /// </summary>
        /// <param name="update">The package information to record.</param>
        /// <returns>If the update is successful, the UpdateResult contains a fresh
        /// copy of the Order that contains the package. If the update is not
        /// successful, the UpdateResult contains the reason why the update failed.</returns>
        /// <remarks>The Shipper client may not include all Update elements. null
        /// elements should be ignored. If the ShipDate is included, then the package
        /// should be marked shipped.</remarks>
        [WebMethod(Description = "Record package information.")]
        public virtual OrderResult UpdatePackage(Update update)
        {
            try
            {
                if (update == null)
                    return null;
                if (string.IsNullOrEmpty(update.OrderId))
                    throw new ApplicationException("Invalid order ID.");
                if (string.IsNullOrEmpty(update.PackageId))
                    throw new ApplicationException("Invalid package ID.");
                return UpdatePackageInStore(update);
            }
            catch (ApplicationException ex)
            {
                return new OrderResult { Success = false, Message = ex.Message };
            }
            catch (Exception ex)
            {
                return new OrderResult {Success = false, Message = ex.ToString()};
            }
        }

        #endregion

        #region Abstract Methods

        protected abstract List<OrderResult> GetNewOrdersFromStore(string lastOrderNumber, int maxCount);
        protected abstract OrderResult GetOrderFromStore(string orderId);
        protected abstract InventoryResult GetOrderInventoryFromStore(string orderId);
        protected abstract List<OrderResult> GetOrdersByRangeFromStore(string startOrderNumber, string endOrderNumber, DateTime? startOrderDate, DateTime? endOrderDate, string lastOrderNumber, int maxCount);
        protected abstract PackageResult GetPackageFromStore(string orderId, string packageId);
        protected abstract List<TrackingProvider> GetTrackingProvidersFromStore();
        protected abstract OrderResult UpdatePackageInStore(Update update);

        #endregion

        #region Helper Methods

        protected static bool ConvertToBoolean(object value)
        {
            if (value == null) return false;
            return value == Convert.DBNull ? false : Convert.ToBoolean(value);
        }

        protected static DateTime? ConvertToDateTime(object value)
        {
            if (value == null) return null;
            return value == Convert.DBNull ? default(DateTime?) : Convert.ToDateTime(value);
        }

        protected static string ConvertToString(object value)
        {
            if (value == null) return string.Empty;
            return value == Convert.DBNull ? string.Empty : Convert.ToString(value);
        }

        protected static string RemoveHtmlTags(string value)
        {
            return Regex.Replace(value, @"</?(\w+)*|/?>", string.Empty).Replace("&nbsp;", " ").Replace("  ", " ");
        }

        protected static string StringOrNull(string value)
        {
            return string.IsNullOrEmpty(value) ? null : value;
        }

        protected static bool StringsAreEqual(string s1, string s2, StringComparison stringComparison)
        {
            if (string.IsNullOrEmpty(s1) && string.IsNullOrEmpty(s2))
                return true;
            return string.Equals(s1, s2, stringComparison);
        }

        #endregion
    }
}